<html>
<!---
This html document will fetch ./nighthawk.json which is expected to reside
in the same directory, and renders a vizualization based on that.
-->

<head>
  <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>

<body>
  <div id="perWorkerDiv"></div>
  <pre id="humanReadableDiv"></pre>

  <script>
    function findGlobalResult(data) {
      /*
      Finds the global aggregated result object in data (Nighthawks parsed json output),
      or null if there isn't one.
      */
      var results = data["results"];
      for (var i = 0; i < results.length; i++) {
        if (results[i].name == "global") {
          return results[i];
        }
      }
      return null;
    }

    function findWorkersResults(data) {
      /*
      Returns the per-worker aggregated result object in data (Nighthawks parsed json output)
      */
      var results = data["results"];
      var worker_results = [];
      for (var i = 0; i < results.length; i++) {
        if (results[i].name != "global") {
          worker_results.push(results[i]);
        }
      }
      return worker_results;
    }

    function findStatisticsById(data, id) {
      /*
      Returns statistic data with id set equal to the passed in id
      argument from the passed in data argument. The data argument
      should point to a result obtained via findXXXResult().
      */
      var statistics = data["statistics"];
      for (var i = 0; i < statistics.length; i++) {
        if (statistics[i].id == id) {
          return statistics[i];
        }
      }
      return null;
    }

    function getPoints(percentiles) {
      /*
      Returns an array containing three vectors, based on the passed
      in percentiles argument:
        [[percentiles],[latencies_in_us],[non-cumulative-counts]]
      */
      var result = [[], [], []];
      last = 0;
      for (var i = 0; i < percentiles.length; i++) {
        var p = percentiles[i];
        result[0].push(p["percentile"] * 100);
        result[1].push(Math.round(parseFloat(p["duration"]) * 1000.0 * 1000.0));
        result[2].push(parseInt(p["count"]) - last);
        last = parseInt(p["count"]);
      }
      return result;
    }

    function getTraces(results, name, stat, line) {
      /*
      Returns the traces for rendering both global and per worker results:
      Both a latency line plot, as well as a bar chart for
      the non-cumulative bucket counts. 
      This will both massage the data as well as prepare the specs for
      what we want to visualization to look like.
      */
      var traces = [];
      for (var i = 0; i < results.length; i++) {
        var result = results[i];
        var statistic = findStatisticsById(result, stat);
        var percentiles = statistic["percentiles"];
        var points = getPoints(percentiles);
        traces.push({
          y: points[0],
          x: points[1],
          name: name + " latency",
          showlegend: true,
          type: "scatter",
          mode: "lines",
          line: line,
          xaxis: "x1",
        });
        traces.push({
          x: points[1],
          y: points[2],
          name: name + " count",
          showlegend: true,
          type: "bar",
          line: line,
          yaxis: "y2",
          shape: "",
        });
      }
      return traces;
    }

    function update(contents, name) {
      /*
      Renders the graph based on string json input.
      The contents argument should be a json string generated by Nighthawk's
      client json output mode. The name argument will be used to annotate lines.
      */
      eval("nhdata = " + contents);
      var global_result = findGlobalResult(nhdata);
      var worker_result = findWorkersResults(nhdata);

      traces = getTraces([global_result], "global." + name, name, {
        color: "rgb(32, 32, 32)",
        width: 3,
        shape: "spline",
      });
      traces = traces.concat(
        getTraces(worker_result, "worker.x." + name, name, {
          color: "rgb(192, 192, 192)",
          width: 3,
          shape: "spline",
        })
      );

      var div = document.createElement("div");
      document.getElementById("perWorkerDiv").appendChild(div);
      Plotly.newPlot(div, traces, layout);
    }

    var data = [];
    var layout = {
      yaxis: {
        range: [0, 100],
        label: "percentile",
        title: {
          text: "percentile",
          font: {
            family: "Courier New, monospace",
            size: 18,
            color: "#7f7f7f",
          },
        },
      },
      xaxis1: {
        type: "log",
        autorange: true,
        title: {
          text: "microseconds",
          font: {
            family: "Courier New, monospace",
            size: 18,
            color: "#7f7f7f",
          },
        },
      },
      yaxis2: {
        autorange: true,
        overlaying: "y",
        side: "right",
        title: {
          text: "count",
          font: {
            family: "Courier New, monospace",
            size: 18,
            color: "#7f7f7f",
          },
        },
      },
    };

    // Fetch ./nighthawk.json and render it when it's ready.
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function () {
      if (this.readyState == 4 && this.status == 200) {
        update(
          this.responseText,
          "benchmark_http_client.request_to_response"
        );
      }
    };
    xmlhttp.open("GET", "nighthawk.json", true);
    xmlhttp.send();

    // Fetch the human readable output and show it verbatim when it's ready.
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function () {
      if (this.readyState == 4 && this.status == 200) {
        document.getElementById(
          "humanReadableDiv"
        ).innerText = this.responseText;
      }
    };
    xmlhttp.open("GET", "nighthawk-human.txt", true);
    xmlhttp.send();
  </script>
</body>

</html>